#include <iostream>
#include <fstream>
#include "sockpp/tcp_connector.h"
#include "stream.pb.h"
#include "opus.h"
#include "OpusDecoder.h"
#include <netinet/tcp.h>

using namespace std;

int main(int argc, char *argv[]) {
    sockpp::socket_initializer sockInit;

    sockpp::tcp_connector conn({argv[1], 3990});
    int yes = 1;
    conn.set_option(IPPROTO_TCP, TCP_NODELAY, (char *) &yes, sizeof(int));
    if (!conn) {
        cerr << "Error connecting to server at "
             << sockpp::inet_address(argv[1], 3990)
             << "\n\t" << conn.last_error_str() << endl;
        return 1;
    }

    OpusEncoder *encoder;
    int err = 0;
    encoder = opus_encoder_create(16000, 1, OPUS_APPLICATION_VOIP, &err);
    if (err) {
        cerr << "create encoder failed:" << err << endl;
        return 2;
    }
    err = opus_encoder_ctl(encoder, OPUS_SET_BITRATE(32000));
    if (err != OPUS_OK) {
        cerr << "set encoder bitrate:" << err << endl;
        throw err;
    }

    // CBR Only
    err = opus_encoder_ctl(encoder, OPUS_SET_VBR(0));
    if (err != OPUS_OK) {
        cerr << "disable encoder vbr:" << err << endl;
        throw err;
    }

    // 20ms framesize
    err = opus_encoder_ctl(encoder, OPUS_SET_EXPERT_FRAME_DURATION(OPUS_FRAMESIZE_20_MS));
    if (err != OPUS_OK) {
        cerr << "set encoder frame duration:" << err << endl;
        throw err;
    }

    std::ifstream in(string("test.pcm"), ios_base::in | ios_base::binary);

    //read 20ms pcm(16k, 16bit). 20 * 16 * 2 = 640bytes
    char buf[640];
    while (true) {
        //read 20ms pcm
        in.read(buf, sizeof(buf));
        bool eof = false;
        if (in.eof())
            eof = true;
        //send frames, or empty opus to indicate end of stream
        auto rd = in.gcount();
        if (rd == sizeof(buf) || eof) {
            unsigned char opusBuf[640];

            //encode to opus
            auto opusLen = 0;
            if (!eof) {
                opusLen = opus_encode(encoder, (opus_int16 *) buf, sizeof(buf) / 2, opusBuf, sizeof(opusBuf));
                if (opusLen < 0) {
                    cerr << "encode failed:" << opusLen << endl;
                    break;
                }
            }

            //serial to protobuf
            yd::audioprocess::Opus opus;
            opus.set_pkg(opusBuf, opusLen);
            string out;
            if (!opus.SerializeToString(&out)) {
                cerr << "serial to protobuf failed" << endl;
                break;
            }
            int32_t len = out.size();

            //write size header
            auto n = conn.write_n(&len, 4);
            if (n != 4) {
                cerr << "short write:" << __LINE__ << endl;
                break;
            }

            //write protobuf object
            n = conn.write_n(out.data(), out.size());
            if (n != out.size()) {
                cerr << "short write:" << __LINE__ << endl;
                break;
            }

            //read size header
            n = conn.read_n(&len, 4);
            if (n != 4) {
                cerr << "short read:" << __LINE__ << endl;
                break;
            }
            if (len == 0) {
                if (eof)
                    break;
                else
                    continue;
            }
            out.resize(len);
            n = conn.read_n(&out[0], len);
            if (n != len) {
                cerr << "short read:" << __LINE__ << endl;
                break;
            }

            yd::audioprocess::AsrResult asr;
            if (!asr.ParseFromString(out)) {
                cerr << "decode protobuf failed:" << __LINE__ << endl;
                break;
            }
            cout << "receive text:" << asr.text() << endl;
        }

        if (eof)
            break;
    }

    opus_encoder_destroy(encoder);
    return 0;
}